import {
  Injectable,
  NotFoundException,
  BadRequestException,
  ForbiddenException,
  InternalServerErrorException,
} from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { createReadStream, createWriteStream, existsSync, mkdirSync, unlinkSync } from 'fs'
import { join } from 'path'
import { v4 as uuidv4 } from 'uuid'
import * as crypto from 'crypto'
import { File } from '../entities/file.entity'
import { UploadFileDto } from '../dto/upload-file.dto'
import { QueryFileDto } from '../dto/query-file.dto'
import { UpdateFileDto } from '../dto/query-file.dto'

// 定义文件类型
interface MulterFile {
  fieldname: string
  originalname: string
  encoding: string
  mimetype: string
  size: number
  destination: string
  filename: string
  path: string
  buffer: Buffer
}

interface CategoryStat {
  category: string
  count: number
  totalSize: number
}

interface FileStats {
  totalFiles: number
  totalSize: number
  avgSize: number
  categoryCount: number
  uploaderCount: number
  categoryStats: CategoryStat[]
}

/**
 * 文件服务
 * 提供文件上传、下载、管理等功能
 */
@Injectable()
export class FileService {
  private readonly uploadDir = join(process.cwd(), 'uploads')
  private readonly maxFileSize = 100 * 1024 * 1024 // 100MB
  private readonly allowedMimeTypes = [
    // 图片
    'image/jpeg',
    'image/png',
    'image/gif',
    'image/webp',
    'image/svg+xml',
    // 文档
    'application/pdf',
    'application/msword',
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    'application/vnd.ms-excel',
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    'text/plain',
    'text/csv',
    // 视频
    'video/mp4',
    'video/avi',
    'video/mov',
    'video/wmv',
    // 音频
    'audio/mpeg',
    'audio/wav',
    'audio/ogg',
    // 压缩包
    'application/zip',
    'application/x-rar-compressed',
    'application/x-7z-compressed',
  ]

  constructor(
    @InjectRepository(File)
    private readonly fileRepository: Repository<File>,
  ) {
    // 确保上传目录存在
    if (!existsSync(this.uploadDir)) {
      mkdirSync(this.uploadDir, { recursive: true })
    }
  }

  /**
   * 上传文件
   * @param file 上传的文件
   * @param uploadData 文件元数据
   * @param userId 上传者ID
   * @returns 文件信息
   */
  async uploadFile(file: MulterFile, uploadData: UploadFileDto, userId: number): Promise<File> {
    // 验证文件大小
    if (file.size > this.maxFileSize) {
      throw new BadRequestException(`文件大小不能超过 ${this.maxFileSize / 1024 / 1024}MB`)
    }

    // 验证文件类型
    if (!this.allowedMimeTypes.includes(file.mimetype)) {
      throw new BadRequestException('不支持的文件类型')
    }

    // 生成唯一文件名
    const extension = file.originalname.split('.').pop()
    const filename = `${uuidv4()}.${extension}`
    const filePath = join(this.uploadDir, filename)

    try {
      // 保存文件到磁盘
      const writeStream = createWriteStream(filePath)
      const readStream = createReadStream(file.path)

      await new Promise<void>((resolve, reject) => {
        readStream.pipe(writeStream)
        writeStream.on('finish', () => resolve())
        writeStream.on('error', reject)
      })

      // 计算文件哈希
      const hash = await this.calculateFileHash(filePath)

      // 检查文件是否已存在（去重）
      const existingFile = await this.fileRepository.findOne({
        where: { hash },
      })

      if (existingFile) {
        // 删除新上传的文件，返回已存在的文件
        unlinkSync(filePath)
        return existingFile
      }

      // 确定文件分类
      const category = this.getFileCategory(file.mimetype)

      // 创建文件记录
      const fileEntity = this.fileRepository.create({
        originalName: file.originalname,
        filename,
        extension,
        mimeType: file.mimetype,
        size: file.size,
        path: filePath,
        url: `/api/files/${filename}`,
        hash,
        status: 'completed',
        category,
        tags: uploadData.tags || [],
        description: uploadData.description,
        isPublic: uploadData.isPublic || false,
        permissions: {
          read: uploadData.readPermissions || [],
          write: uploadData.writePermissions || [],
          delete: uploadData.deletePermissions || [],
        },
        uploaderId: userId,
      })

      return await this.fileRepository.save(fileEntity)
    } catch (error) {
      // 清理文件
      if (existsSync(filePath)) {
        unlinkSync(filePath)
      }
      throw new InternalServerErrorException('文件上传失败')
    }
  }

  /**
   * 获取文件列表
   * @param query 查询参数
   * @param userId 当前用户ID
   * @returns 文件列表和总数
   */
  async getFiles(query: QueryFileDto, userId: number): Promise<{ data: File[]; total: number }> {
    const {
      page = 1,
      limit = 10,
      search,
      category,
      mimeType,
      status,
      isPublic,
      tags,
      uploaderId,
      minSize,
      maxSize,
      startDate,
      endDate,
      sortBy = 'createdAt',
      sortOrder = 'DESC',
    } = query

    const queryBuilder = this.fileRepository
      .createQueryBuilder('file')
      .leftJoinAndSelect('file.uploader', 'uploader')
      .where('1=1')

    // 权限过滤：只能看到公开文件或自己有权限的文件
    queryBuilder.andWhere(
      '(file.isPublic = :isPublic OR file.uploaderId = :userId OR JSON_CONTAINS(file.permissions->>"$.read", :userIdStr))',
      { isPublic: true, userId, userIdStr: `"${userId}"` },
    )

    // 搜索条件
    if (search) {
      queryBuilder.andWhere('(file.originalName LIKE :search OR file.description LIKE :search)', {
        search: `%${search}%`,
      })
    }

    if (category) {
      queryBuilder.andWhere('file.category = :category', { category })
    }

    if (mimeType) {
      queryBuilder.andWhere('file.mimeType = :mimeType', { mimeType })
    }

    if (status) {
      queryBuilder.andWhere('file.status = :status', { status })
    }

    if (isPublic !== undefined) {
      queryBuilder.andWhere('file.isPublic = :isPublic', { isPublic })
    }

    if (tags && tags.length > 0) {
      queryBuilder.andWhere('JSON_OVERLAPS(file.tags, :tags)', { tags: JSON.stringify(tags) })
    }

    if (uploaderId) {
      queryBuilder.andWhere('file.uploaderId = :uploaderId', { uploaderId })
    }

    if (minSize) {
      queryBuilder.andWhere('file.size >= :minSize', { minSize })
    }

    if (maxSize) {
      queryBuilder.andWhere('file.size <= :maxSize', { maxSize })
    }

    if (startDate) {
      queryBuilder.andWhere('file.createdAt >= :startDate', { startDate })
    }

    if (endDate) {
      queryBuilder.andWhere('file.createdAt <= :endDate', { endDate })
    }

    // 排序
    queryBuilder.orderBy(`file.${sortBy}`, sortOrder)

    // 分页
    const skip = (page - 1) * limit
    queryBuilder.skip(skip).take(limit)

    const [data, total] = await queryBuilder.getManyAndCount()

    return { data, total }
  }

  /**
   * 根据ID获取文件
   * @param id 文件ID
   * @param userId 当前用户ID
   * @returns 文件信息
   */
  async getFileById(id: number, userId: number): Promise<File> {
    const file = await this.fileRepository.findOne({
      where: { id },
      relations: ['uploader'],
    })

    if (!file) {
      throw new NotFoundException('文件不存在')
    }

    // 检查访问权限
    if (!this.canAccessFile(file, userId, 'read')) {
      throw new ForbiddenException('没有访问权限')
    }

    // 更新最后访问时间
    file.lastAccessedAt = new Date()
    await this.fileRepository.save(file)

    return file
  }

  /**
   * 根据文件名获取文件
   * @param filename 文件名
   * @param userId 当前用户ID
   * @returns 文件信息
   */
  async getFileByFilename(filename: string, userId: number): Promise<File> {
    const file = await this.fileRepository.findOne({
      where: { filename },
      relations: ['uploader'],
    })

    if (!file) {
      throw new NotFoundException('文件不存在')
    }

    // 检查访问权限
    if (!this.canAccessFile(file, userId, 'read')) {
      throw new ForbiddenException('没有访问权限')
    }

    // 更新最后访问时间和下载次数
    file.lastAccessedAt = new Date()
    file.downloadCount += 1
    await this.fileRepository.save(file)

    return file
  }

  /**
   * 更新文件信息
   * @param id 文件ID
   * @param updateData 更新数据
   * @param userId 当前用户ID
   * @returns 更新后的文件信息
   */
  async updateFile(id: number, updateData: UpdateFileDto, userId: number): Promise<File> {
    const file = await this.getFileById(id, userId)

    // 检查写权限
    if (!this.canAccessFile(file, userId, 'write')) {
      throw new ForbiddenException('没有修改权限')
    }

    // 更新文件信息
    Object.assign(file, updateData)

    // 更新权限信息
    if (updateData.readPermissions) {
      file.permissions.read = updateData.readPermissions
    }
    if (updateData.writePermissions) {
      file.permissions.write = updateData.writePermissions
    }
    if (updateData.deletePermissions) {
      file.permissions.delete = updateData.deletePermissions
    }

    return await this.fileRepository.save(file)
  }

  /**
   * 删除文件
   * @param id 文件ID
   * @param userId 当前用户ID
   * @returns 删除结果
   */
  async deleteFile(id: number, userId: number): Promise<{ success: boolean }> {
    const file = await this.getFileById(id, userId)

    // 检查删除权限
    if (!this.canAccessFile(file, userId, 'delete')) {
      throw new ForbiddenException('没有删除权限')
    }

    try {
      // 删除物理文件
      if (existsSync(file.path)) {
        unlinkSync(file.path)
      }

      // 删除数据库记录
      await this.fileRepository.remove(file)

      return { success: true }
    } catch (error) {
      throw new InternalServerErrorException('文件删除失败')
    }
  }

  /**
   * 批量删除文件
   * @param ids 文件ID数组
   * @param userId 当前用户ID
   * @returns 删除结果
   */
  async deleteFiles(ids: number[], userId: number): Promise<{ success: boolean; deletedCount: number }> {
    let deletedCount = 0

    for (const id of ids) {
      try {
        await this.deleteFile(id, userId)
        deletedCount++
      } catch (error) {
        console.error(`删除文件 ${id} 失败:`, error)
      }
    }

    return { success: true, deletedCount }
  }

  /**
   * 获取文件统计信息
   * @param userId 当前用户ID
   * @returns 统计信息
   */
  async getFileStats(userId: number): Promise<FileStats | undefined> {
    const stats = await this.fileRepository
      .createQueryBuilder('file')
      .select([
        'COUNT(*) as totalFiles',
        'SUM(file.size) as totalSize',
        'AVG(file.size) as avgSize',
        'COUNT(DISTINCT file.category) as categoryCount',
        'COUNT(DISTINCT file.uploaderId) as uploaderCount',
      ])
      .where(
        'file.uploaderId = :userId OR file.isPublic = :isPublic OR JSON_CONTAINS(file.permissions->>"$.read", :userIdStr)',
        { userId, isPublic: true, userIdStr: `"${userId}"` },
      )
      .getRawOne<FileStats>()

    // 按分类统计
    const categoryStats = await this.fileRepository
      .createQueryBuilder('file')
      .select(['file.category', 'COUNT(*) as count', 'SUM(file.size) as totalSize'])
      .where(
        'file.uploaderId = :userId OR file.isPublic = :isPublic OR JSON_CONTAINS(file.permissions->>"$.read", :userIdStr)',
        { userId, isPublic: true, userIdStr: `"${userId}"` },
      )
      .groupBy('file.category')
      .getRawMany<CategoryStat>()
    if (stats !== undefined) {
      return {
        ...stats,
        categoryStats,
      }
    }
    return undefined
  }

  /**
   * 检查文件访问权限
   * @param file 文件信息
   * @param userId 用户ID
   * @param permission 权限类型
   * @returns 是否有权限
   */
  private canAccessFile(file: File, userId: number, permission: 'read' | 'write' | 'delete'): boolean {
    // 文件所有者有所有权限
    if (file.uploaderId === userId) {
      return true
    }

    // 公开文件可读
    if (file.isPublic && permission === 'read') {
      return true
    }

    // 检查特定权限
    if (file.permissions && file.permissions[permission]) {
      return file.permissions[permission].includes(userId)
    }

    return false
  }

  /**
   * 计算文件哈希值
   * @param filePath 文件路径
   * @returns 文件哈希值
   */
  private async calculateFileHash(filePath: string): Promise<string> {
    return new Promise((resolve, reject) => {
      const hash = crypto.createHash('sha256')
      const stream = createReadStream(filePath)

      stream.on('data', (data) => hash.update(data))
      stream.on('end', () => resolve(hash.digest('hex')))
      stream.on('error', reject)
    })
  }

  /**
   * 根据MIME类型确定文件分类
   * @param mimeType MIME类型
   * @returns 文件分类
   */
  private getFileCategory(mimeType: string): string {
    if (mimeType.startsWith('image/')) return 'image'
    if (mimeType.startsWith('video/')) return 'video'
    if (mimeType.startsWith('audio/')) return 'audio'
    if (mimeType.startsWith('application/pdf') || mimeType.includes('document') || mimeType.startsWith('text/'))
      return 'document'
    if (mimeType.includes('zip') || mimeType.includes('rar') || mimeType.includes('7z')) return 'archive'
    return 'other'
  }
}
